use std::rc::Rc;

use objc2::rc::{autoreleasepool, Id};
use objc2::runtime::{AnyObject, ProtocolObject};
use objc2::{declare_class, msg_send, msg_send_id, mutability, ClassType, DeclaredClass};
use objc2_app_kit::{NSBackingStoreType, NSEvent, NSEventType, NSResponder, NSWindow, NSWindowDelegate, NSWindowStyleMask};
use objc2_foundation::{CGPoint, CGRect, CGSize, MainThreadMarker, NSNotification, NSObject, NSObjectProtocol, NSString};

use super::{Factor, Vars, WinEvent, WinRef, Window, XLoopView};

declare_class!(
	#[derive(Debug)]
	pub struct XLoopWindow;
	unsafe impl ClassType for XLoopWindow {
		#[inherits(NSResponder, NSObject)]
		type Super = NSWindow;
		type Mutability = mutability::MainThreadOnly;
		const NAME: &'static str = "XLoopWindow";
	}
	impl DeclaredClass for XLoopWindow {
		type Ivars = (Vars,Id<WinDelegate>);
	}
	unsafe impl XLoopWindow {
		#[method(sendEvent:)]
		fn send_event(&self, event: &NSEvent) {
			self.send_event_(event);
			unsafe { msg_send![super(self), sendEvent: event] }
		}
		#[method(updateProxy:)]
		fn update_proxy(&self,value:i64){ self.update_proxy_(value) }
		#[method(canBecomeMainWindow)]
		fn can_become_main_window(&self) -> bool { true }
		#[method(canBecomeKeyWindow)]
		fn can_become_key_window(&self) -> bool { true }
	}
);
impl XLoopWindow {
	pub fn new(attr: <Window as types::Win>::Attr, rep: impl types::WinRep<Window> + 'static) -> Id<XLoopWindow> {
		let rep = Rc::new(rep);
		autoreleasepool(|_| {
			let mtm = MainThreadMarker::new().unwrap();
			let vars = Vars {
				rep,
				refresh: true.into(),
				marked_text: Default::default(),
			};

			let rep = mtm.alloc().set_ivars(vars.clone());
			let rep: Id<WinDelegate> = unsafe { msg_send_id![super(rep), init] };
			let rect = CGRect::new(
				if let Some((x, y)) = attr.pos {
					CGPoint::new(x as _, y as _)
				} else {
					CGPoint::new(0., 0.)
				},
				if let Some((w, h)) = attr.size {
					CGSize::new(w as _, h as _)
				} else {
					CGSize::new(800., 600.)
				},
			);

			let mut style = NSWindowStyleMask::Titled;
			if attr.closable {
				style |= NSWindowStyleMask::Closable;
			}
			if attr.maximizable {
				// not surport
			}
			if attr.minimizable {
				style |= NSWindowStyleMask::Miniaturizable;
			}
			if attr.resizable {
				style |= NSWindowStyleMask::Resizable;
			}
			let window: Id<XLoopWindow> = unsafe {
				msg_send_id![
					super(mtm.alloc().set_ivars((vars.clone(),rep.clone()))),
					initWithContentRect: rect,
					styleMask: style,
					backing: NSBackingStoreType::NSBackingStoreBuffered,
					defer: false,
				]
			};
			window.setDelegate(Some(ProtocolObject::from_ref(&*rep.clone())));

			window.setTitle(&NSString::from_str(&attr.title));

			let view = mtm.alloc().set_ivars(vars.clone());
			let view: Id<XLoopView> = unsafe { msg_send_id![super(view), init] };
			// unsafe { view.as_super().setPaused(true) };
			// unsafe { view.setEnableSetNeedsDisplay(true) };

			window.setContentView(Some(&view));

			window.setInitialFirstResponder(Some(&view));

			if attr.pos.is_none() {
				window.center();
			}

			rep.ivars().rep.on_create(WinRef(&window, &view));

			window
		})
	}
	fn send_event_(&self, event: &NSEvent) {
		let event_type = unsafe { event.r#type() };
		if event_type == NSEventType::MouseMoved {
			if let Some(view) = self.contentView() {
				self.ivars().0.rep.on_mouse(WinRef(&self, &view), WinEvent(event));
			}
		}
	}
	fn update_proxy_(&self, value: i64) {
		if let Some(view) = self.contentView() {
			self.ivars().0.rep.on_proxy(WinRef(&self, &view), value);
		}
	}
}

declare_class!(
	#[derive(Debug)]
	pub	struct WinDelegate;
	unsafe impl ClassType for WinDelegate {
		type Super = NSObject;
		type Mutability = mutability::MainThreadOnly;
		const NAME: &'static str = "XLoopWindowDelegate";
	}
	impl DeclaredClass for WinDelegate {
		type Ivars = Vars;
	}
	unsafe impl NSObjectProtocol for WinDelegate {}
	unsafe impl NSWindowDelegate for WinDelegate {
		#[method(windowShouldClose:)]
		unsafe fn window_should_close(&self, window: &NSWindow) -> bool{
			self.should_close(window)
		}
		#[method(windowDidResize:)]
		unsafe fn window_did_resize(&self, notification: &NSNotification){
			self.did_resize(notification)
		}
	}
);
impl WinDelegate {
	fn should_close(&self, window: &NSWindow) -> bool {
		if let Some(view) = window.contentView() {
			self.ivars().rep.on_req_close(WinRef(&window, &view))
		} else {
			true
		}
	}
	fn did_resize(&self, notification: &NSNotification) {
		if let Some(window) = unsafe { notification.object() } {
			let window: &NSWindow = unsafe { &*(&*window as *const AnyObject).cast() };
			if let Some(view) = window.contentView() {
				let frame = view.frame();
				let factor: Factor = Factor::from(window);
				let size = (factor.to_u32(frame.size.width), factor.to_u32(frame.size.height));
				let winref = WinRef(&window, &view);
				self.ivars().rep.on_resize(winref, size);
				use types::WinRef as _;
				winref.fresh("did_resize");
			}
		}
	}
}
